package com.cy.train.business.service.imp;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.cy.train.business.domain.*;
import com.cy.train.business.enums.ConfirmOrderStatusEnum;
import com.cy.train.business.enums.SeatColEnum;
import com.cy.train.business.enums.SeatTypeEnum;
import com.cy.train.business.mapper.ConfirmOrderMapper;
import com.cy.train.business.req.ConfirmOrderDoReq;
import com.cy.train.business.req.ConfirmOrderQueryReq;
import com.cy.train.business.req.ConfirmOrderTicketReq;
import com.cy.train.business.resp.ConfirmOrderQueryResp;
import com.cy.train.business.service.ConfirmOrderService;
import com.cy.train.business.service.DailyTrainCarriageService;
import com.cy.train.business.service.DailyTrainSeatService;
import com.cy.train.business.service.DailyTrainTicketService;
import com.cy.train.common.context.LoginMemberContext;
import com.cy.train.common.exception.BusinessException;
import com.cy.train.common.exception.BusinessExceptionEnum;
import com.cy.train.common.resp.PageResp;
import com.cy.train.common.util.SnowUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Description:
 * @Date: 2024/2/27 14:48
 * @Author: cyzhang
 */
@Service
public class ConfirmOrderServiceImp implements ConfirmOrderService {
    private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderServiceImp.class);

    @Autowired
    private ConfirmOrderMapper confirmOrderMapper;

    @Autowired
    private DailyTrainTicketService dailyTrainTicketService;

    @Autowired
    private DailyTrainCarriageService dailyTrainCarriageService;

    @Autowired
    private DailyTrainSeatService dailyTrainSeatService;

    @Autowired
    private AfterConfirmOrderService afterConfirmOrderService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void save(ConfirmOrderDoReq req) {
        DateTime now = DateTime.now();
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
        if(ObjectUtil.isNull(confirmOrder.getId())) {
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.insert(confirmOrder);
        } else {
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.updateByPrimaryKeySelective(confirmOrder);
        }

    }

    @Override
    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.setOrderByClause("id desc");

        LOG.info("当前页：{}", req.getPage());
        LOG.info("每页数量：{}", req.getSize());

        PageHelper.startPage(req.getPage(), req.getSize());
        List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
        LOG.info("总条数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<ConfirmOrderQueryResp> confirmOrderQueryRespList = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
        PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(confirmOrderQueryRespList);

        return pageResp;
    }

    @Override
    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void doConfirm(ConfirmOrderDoReq req) {
        //忽略业务数据校验，如：车次是否存在，余票是否存在，车次是否在有效期，tickets.length>0，同天车次同一人只能购买一次
        String key = req.getDate() + "-" + req.getTrainCode();
        Boolean isAbsent = redisTemplate.opsForValue().setIfAbsent(key, key, 5, TimeUnit.SECONDS);
        if(isAbsent) {
            LOG.info("成功抢到锁！");
        } else {
            LOG.info("没有抢到锁！");
            throw new BusinessException(BusinessExceptionEnum.TICKET_LOCK_ERROR);
        }
        //初始化确认订单表
        Date date = req.getDate();
        String trainCode = req.getTrainCode();
        String start = req.getStart();
        String end = req.getEnd();

        DateTime now = new DateTime();
        ConfirmOrder confirmOrder = new ConfirmOrder();
        confirmOrder.setId(SnowUtil.getSnowflakeNextId());
        confirmOrder.setMemberId(LoginMemberContext.getId());
        confirmOrder.setDate(date);
        confirmOrder.setTrainCode(trainCode);
        confirmOrder.setStart(start);
        confirmOrder.setEnd(end);
        confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
        confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
        confirmOrder.setCreateTime(now);
        confirmOrder.setUpdateTime(now);
        confirmOrder.setTickets(JSON.toJSONString(req.getTickets()));
        confirmOrderMapper.insert(confirmOrder);
        //查询真实余票
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectUnique(date, trainCode, start, end);
        //扣减余票（预扣减），判断余票是否充足
        reduceTickets(req, dailyTrainTicket);

        //最终选座列表
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();

        //选座
        //构造选座列表，计算偏移值
        ConfirmOrderTicketReq ticketReq0 = req.getTickets().get(0);
        if(StrUtil.isNotBlank(ticketReq0.getSeat())) {
            LOG.info("需要选座");
            String seatTypeCode = ticketReq0.getSeatTypeCode();
            List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(seatTypeCode);
            List<String> referSeatList = new ArrayList<>();
            for(int i = 1; i < 3; i++) {
                for(SeatColEnum seatColEnum : colEnumList) {
                    referSeatList.add(seatColEnum.getCode() + i);
                }
            }
            LOG.info("参考数组：{}", referSeatList);

            List<Integer> absoluteOffsetArray = new ArrayList<>();
            for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
                absoluteOffsetArray.add(referSeatList.indexOf(ticketReq.getSeat()));
            }
            LOG.info("绝对偏移数组：{}", absoluteOffsetArray);
            List<Integer> relativeOffsetArray = new ArrayList<>();
            Integer baseOffset = absoluteOffsetArray.get(0);
            for(int i = 0; i < absoluteOffsetArray.size(); i++) {
                relativeOffsetArray.add(absoluteOffsetArray.get(i) - baseOffset);
            }
            LOG.info("相对偏移数组：{}", relativeOffsetArray);
            getSeat(finalSeatList, date, trainCode, seatTypeCode, req.getTickets().get(0).getSeat(), relativeOffsetArray, dailyTrainTicket.getStartIndex(), dailyTrainTicket.getEndIndex());
        } else {
            LOG.info("不需要选座");
            for(ConfirmOrderTicketReq ticketReq : req.getTickets()) {
                getSeat(finalSeatList, date, trainCode, ticketReq.getSeatTypeCode(), null, null, dailyTrainTicket.getStartIndex(), dailyTrainTicket.getEndIndex());
            }
        }
        LOG.info("最终选座：{}", finalSeatList);

        try {
            afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, req.getTickets(), confirmOrder);
        } catch (Exception e) {
            LOG.error("保存购票信息失败：{}", e);
            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_SAVE_ERROR);
        }
    }

    private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String firstSeat, List<Integer> offsetArray, Integer startIndex, Integer endIndex) {
        List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.getCarriageByTrainCode(date, trainCode, seatType);
        //一个车厢一个车厢地查找符合条件的座位
        LOG.info("符合条件的车厢数：{}", carriageList.size());
        List<DailyTrainSeat> getSeatList = new ArrayList<>();
        for(DailyTrainCarriage carriage : carriageList) {
            getSeatList.clear();
            List<DailyTrainSeat> seatList = dailyTrainSeatService.getSeatByCarriage(date, trainCode, carriage.getIndex());
            LOG.info("车厢：{}，有座位{}个", carriage.getIndex(), seatList.size());
            for(DailyTrainSeat dailyTrainSeat : seatList) {
                String col = dailyTrainSeat.getCol();
                Integer seatIndex = dailyTrainSeat.getCarriageIndex();
                boolean alreadyChooseFlag = false;
                for(DailyTrainSeat finalSeat : finalSeatList) {
                    if(dailyTrainSeat.getId().equals(finalSeat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if(alreadyChooseFlag) {
                    LOG.info("当前座位{}已被选中, 跳过判断下一个座位", seatIndex);
                    continue;
                }
                if(StrUtil.isNotBlank(firstSeat)) {
                    String goalCol = firstSeat.substring(0, 1);
                    LOG.info("需要选座，目标座位：{}", goalCol);
                    if(!goalCol.equals(col)) {
                        LOG.info("{}非目标座位，跳过", col);
                        continue;
                    } else {
                        LOG.info("{}是目标座位，进行选座", col);
                    }
                } else {
                    LOG.info("不需要选座");
                }
                boolean isChoose = calSell(dailyTrainSeat, startIndex, endIndex);
                if(isChoose) {
                    LOG.info("成功选中座位:{}", dailyTrainSeat.getCarriageSeatIndex());
                    getSeatList.add(dailyTrainSeat);
                } else {
                    continue;
                }

                boolean isGetAllSeat = true;
                if(CollUtil.isNotEmpty(offsetArray)) {
                    LOG.info("存在偏移数组{}，继续选座", offsetArray);
                    for(int i = 1; i < offsetArray.size(); i++) {
                        Integer offset = offsetArray.get(i);
                        int nextIndex = offset + seatIndex;
                        LOG.info("偏移至座位{}", nextIndex);
                        if(nextIndex >= seatList.size()) {
                            //不能跨车厢选座
                            LOG.info("座位{}不可选，偏移后的索引超出本车厢座位数", nextIndex);
                            isGetAllSeat = false;
                            break;
                        }
                        DailyTrainSeat nextDailyTrainSeat = seatList.get(nextIndex);
                        boolean isChooseNext = calSell(nextDailyTrainSeat, startIndex, endIndex);
                        if(!isChooseNext) {
                            isGetAllSeat = false;
                            break;
                        } else {
                            getSeatList.add(nextDailyTrainSeat);
                        }
                    }
                }

                if(!isGetAllSeat) {
                    getSeatList.clear();
                } else {
                    finalSeatList.addAll(getSeatList);
                    return;
                }
            }
        }
    }

    private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        if(Integer.parseInt(sellPart) > 0) {
            LOG.info("座位{}在本次车站区间{}~{}已售过票，不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        } else {
            LOG.info("座位{}在本次车站区间{}~{}未售过票，可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            String curSell = sellPart.replace('0', '1');
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            String newSell= StrUtil.fillBefore(NumberUtil.getBinaryStr(newSellInt), '0', sell.length());
            LOG.info("座位{}被选中，原售票信息：{}，车站区间：{}~{}，最终售票信息：{}", dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, newSell);
            dailyTrainSeat.setSell(newSell);
            return true;
        }
    }

    private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
        List<ConfirmOrderTicketReq> reqTickets = req.getTickets();
        for(ConfirmOrderTicketReq ticketReq : reqTickets) {
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seatTypeEnum = SeatTypeEnum.getEnumByCode(seatTypeCode);
            LOG.info("seatTypeEnum: {}", seatTypeEnum.getDesc());
            switch (seatTypeEnum) {
                case YDZ:
                    Integer remainTicketYDZ = dailyTrainTicket.getYdz() - 1;
                    if (remainTicketYDZ < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_EMPTY);
                    } else {
                        dailyTrainTicket.setYdz(remainTicketYDZ);
                    }
                    break;
                case EDZ:
                    Integer remainTicketEDZ = dailyTrainTicket.getEdz() - 1;
                    if (remainTicketEDZ < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_EMPTY);
                    } else {
                        dailyTrainTicket.setEdz(remainTicketEDZ);
                    }
                    break;
                case RW:
                    Integer remainTicketRW = dailyTrainTicket.getRw() - 1;
                    if (remainTicketRW < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_EMPTY);
                    } else {
                        dailyTrainTicket.setRw(remainTicketRW);
                    }
                    break;
                case YW:
                    Integer remainTicketYW = dailyTrainTicket.getYw() - 1;
                    if (remainTicketYW < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_EMPTY);
                    } else {
                        dailyTrainTicket.setYw(remainTicketYW);
                    }
                    break;
            }
        }
    }
}
